-- UnrealDungeonM
-- Created by panyl Jun/18/2015
-- 夹层迷宫（不真实的迷宫）

module("UnrealDungeonM", package.seeall);

-- 夹层迷宫信息
local unrealDungeonTable = {};
local unrealSceneTable = {};
local unrealSceneList  = {};
local ALTAR_RUINS_ID = {16, 17, 18, 19};

-- 内部函数声明
local initMonster;
local whenLeaveUnreal;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    -- 转换下unreal_dungeon信息表
    local unreal_dungeon_CSV = get_unreal_dungeon_CSV();
    for i = 1, #unreal_dungeon_CSV do
        unrealDungeonTable[unreal_dungeon_CSV[i].id] = unreal_dungeon_CSV[i];
    end

    unreal_dungeon_CSV = {};

    -- 转换迷宫夹层场景配置表
    local unreal_scene_CSV = get_unreal_scene_CSV();
    for i = 1, #unreal_scene_CSV do
        local id = unreal_scene_CSV[i].id;
        local sceneId = unreal_scene_CSV[i].scene_id;

        unrealSceneTable[id] = unreal_scene_CSV[i];

        -- 根据场景编号存放资源
        if unrealSceneList[sceneId] == nil then
            unrealSceneList[sceneId] = {};
        end
        table.insert(unrealSceneList[sceneId], id);
    end

    unreal_scene_CSV = {};
end

-- 初始化
function init()
    loadCsv();

    -- 注册怪物初始化回调
    MonsterM.registerInitCallback("UnrealDungeonM", initMonster);
end

-- 初始化怪物的回调
initMonster = function(monster)
    -- 如果不是在夹层迷宫中
    if not isInUnreal() then
        return;
    end

    local unrealId = getUnrealId();

    local dbase = query(unrealId, "monster_init");

    monster.dbase:aborb(dbase);

    -- class_id有可能已经变掉了
    if type(dbase["class_id"]) == "number" then
        monster:setClassId(dbase["class_id"]);
    end
end

-- 检索
function query(unrealId, path)
    local m = unrealDungeonTable[unrealId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 检索场景资源配置
function queryResource(resourceId, path)
    local m = unrealSceneTable[resourceId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 获取该场景下的所有资源配置
function getResourceBySceneId(sceneId)
    return unrealSceneList[sceneId];
end

-- 备份当前地牢数据
function bakDungeonData()
    local dungeon = DungeonM.getDungeonContainer();
    if not dungeon then
        return false;
    end

    -- 将主地牢数据备份起来
    local dungeonBak = table.deepcopy(dungeon);
    ME.user.dbase:set("dungeon_bak", dungeonBak);

    -- 将主地牢的上下文数据备份起来
    local dungeonContext = ME.user.dbase:query("dungeon_context");
    local dungeonContextBak = table.deepcopy(dungeonContext);
    ME.user.dbase:set("dungeon_context_bak", dungeonContextBak);

    -- 移除上下文数据
    ME.user.dbase:delete("dungeon_context");

    return true;
end

-- 还原当前地牢数据
function restoreDungeonData()
    local dungeonContextBak = ME.user.dbase:query("dungeon_context_bak");

    -- 如果是角斗台，并且玩家已经胜利，则需要移除角斗台格子
    if (ArenaM.isInArena() and ArenaM.hasWon())
     or DungeonM.getDungeonMixedField("sky_coffin_reward") == 1  then
        local context = dungeonContextBak["context"];
        for i = 1, #context do
            if type(context[i]) == "table" then
                local class = context[i].class;
                if DungeonM.query(class, "type") == GRID_TYPE_ARENA then
                    -- 标记为已失效
                    context[i].state = GRID_STATE_DISABLE;
                end

                -- 天空副本的血腥地窖消失
                if DungeonM.query(class, "type") == GRID_TYPE_SKY_BLOODYCAVE then
                    -- 标记为已失效
                    context[i].state = GRID_STATE_DISABLE;
                end
            end
        end
    end

    local dungeonBak = ME.user.dbase:query("dungeon_bak");
    if not dungeonBak then
        return false;
    end

    local dungeon = table.deepcopy(dungeonBak);
    DungeonM.setDungeon(dungeon);

    -- 还原主地牢上下文数据
    ME.user.dbase:set("dungeon_context", table.deepcopy(dungeonContextBak));

    -- 移除地牢备份数据
    ME.user.dbase:delete("dungeon_bak");
    ME.user.dbase:delete("dungeon_context_bak");

    return true;
end

-- 初始化夹层迷宫格子
function initData(dungeonId, unrealId)
    local grids = fetchGrids(dungeonId, unrealId);

    -- 备份下当前地牢数据
    bakDungeonData();

    -- 更新当前地牢数据
    local dungeon = DungeonM.getDungeonContainer();
    local layer   = DungeonM.currentLayer();
    dungeon["combats"][layer] = grids;
    dungeon["keyPos"][layer] = nil;
    dungeon["unrealId"] = unrealId;

    DungeonM.setDungeon(dungeon);
end

-- 开始迷宫
function startDungeon(unrealId, pos)
    local dungeonId = DungeonM.getDungeonId();
    assert(type(dungeonId) == "number", "dungeonId必须为number类型");
    assert(type(unrealId) == "number", "unrealId必须为number类型");

    local grid;
    if type(pos) == "number" then
        grid = DungeonM.getGridByPos(pos);
        if not grid then
            trace("UnrealDungeonM", "格子(%d)不存在。", pos);
            return false;
        end

        if grid.state == GRID_STATE_DISABLE then
            trace("UnrealDungeonM", "格子(%d)已经失效。", pos);
            return false;
        end

        local dbase = query(unrealId, "dbase");
        if grid.type ~= GRID_TYPE_ARENA and
           grid.type ~= GRID_TYPE_SKY_BLOODYCAVE and
           dbase["repeat_enter"] ~= 1 then
            -- 如果是普通夹层，需要标记格子已失效
            -- 必须在暂离之前设置失效，否则从夹层中出来后，格子状态就不是失效的了
            grid:changeState(GRID_STATE_DISABLE);
        end
    end

    -- 异步加载原因增加设置一个临时变量 -- SLIMEC-2142
    ME.user.dbase:setTemp("startUnrealDungeon", unrealId);

    -- 需要先暂停当前迷宫
    DungeonM.pauseDungeon();

    -- 初始化数据
    initData(dungeonId, unrealId);

    -- 开始迷宫
    DungeonM.startDungeon();

    local dbase = query(unrealId, "dbase");
    if type(dbase["enter_stat"]) == "string" then
        -- 统计进入次数
        BuildingBonusM.addFieldStat(dbase["enter_stat"], 1);
    end

    if grid then
        -- 添加同步操作缓存
        DungeonM.addAction({["cmd"] = "start_unreal", ["pos"] =  pos, ["data"] = unrealId, });
    end

    -- 抛出事件
    EventMgr.fire(event.ENTER_UNREAL_DUNGEON, { ["unrealId"] = unrealId, ["pos"] = pos, });
    return true, true;
end

-- 离开迷宫
function leaveDungeon()
    -- 如果不在夹层迷宫中
    if not isInUnreal() then
        return false;
    end

    local unrealId = getUnrealId();
    doLeaveUnrealTip(unrealId);
    local leaveTalk = LeaveUnrealTalk(unrealId);

    -- 还原下上下文
    restoreDungeonData();

    -- 还原迷宫
    DungeonM.restoreDungeonCombat(true);
    whenLeaveUnreal();

    EventMgr.fire(event.LEAVE_UNREAL_DUNGEON, { ["unrealId"] = unrealId, ["leaveTalk"] = leaveTalk, });

    return true;
end

-- 离开夹层提示
function doLeaveUnrealTip(unrealId)
    local dbase = UnrealDungeonM.query(unrealId, "dbase");

    -- 如果是可反复进入的夹层
    if type(dbase) == 'table' and dbase["repeat_enter"] == 1  then
        -- 如果有宝箱怪存活，提示宝箱怪逃跑了
        if isBoxMonsterAlive() then
            doAlert(getLocStr("box_monster_flee"));
        end
    end
end

-- 离开夹层播放对话
function LeaveUnrealTalk(unrealId)
    local dbase = UnrealDungeonM.query(unrealId, "dbase");

    -- 离开沼泽洞窟时，根据侍卫队长存活情况播放对话
    if dbase["swamp_cave"] == 1 then
        local classId = dbase["guards_leader_id"];
        if classId and #SummonM.findSummonsByClassId(classId) > 0 then
            return "guards_leader_talk3";
        end
    end
end

-- 是否有宝箱怪存活
function isBoxMonsterAlive()
    local grids = DungeonM.getCurrentDungeon();

    for _, grid in pairs(grids) do
        if grid:isMonster() and
            not grid.monster:isDead() then
            local dbase = MonsterM.query(grid.monster.classId, "dbase");
            if dbase["box_monster"] == 1 then
                return true;
            end
        end
    end
    return false;
end

-- 是否在夹层迷宫中
function isInUnreal()
    return type(getUnrealId()) == "number";
end

-- 获取夹层迷宫id
function getUnrealId()
    local dungeon = DungeonM.getDungeonContainer();
    if not dungeon then
        return;
    end

    return dungeon["unrealId"];
end

-- 抽取夹层迷宫格子
function fetchGrids(dungeonId, unrealId)
    local dbase = query(unrealId, "dbase");

    -- 如果配置了抽取公式
    if dbase["fetch_formula"] then
        return FormulaM.invoke(dbase["fetch_formula"], dungeonId, unrealId);
    end

    -- 如果是boss夹层（勇者斗恶龙的夹层除外）
    if dbase["is_boss"] == 1 and dbase["brave_vs_dragon"] ~= 1 then
        local dragon_boss_ins = ME.user.dbase:query("dragon_boss_ins");

        if type(dragon_boss_ins) == "table" then
            local arr = {};
            for _, info in ipairs(dragon_boss_ins) do
                if type(info) == "table" then
                    table.insert(arr, info);
                end
            end

            return arr;
        end

        local arr = fetchBossGrids(dungeonId, unrealId);
        if arr and #arr > 0 then
            return arr;
        end
    end

    local ret = {};
    local layout = query(unrealId, "layout");

    if dbase["fitter_hill"] == 1 then
        layout = fitterLayout(layout);
    end

    local appointElement = query(unrealId, "dbase")["appoint_element"];

    for pos = 1, GRID_SIZE do
        -- 客户端的位置应该比服务器+1
        local class = layout[pos - 1];

        if class then
            if appointElement ~= nil and appointElement[pos - 1] ~= nil and
               class == DungeonM.queryElement(appointElement[pos - 1], "class") then
                ret[pos] = { [class] = appointElement[pos - 1]};
            else
                ret[pos] = { [class] = fetchElement(class, dungeonId, unrealId), };
            end
        else
            ret[pos] = { [GRID_TYPE_EMPTY] = 0, };
        end
    end

    -- 如果是角斗台，还要抽取同玩家角斗的冈布奥及观众
    if dbase["is_arena"] == 1 then
        local roleGrids = ArenaM.fetchRoleGrids();
        ret = table.add(ret, roleGrids);
    end

    if checkReplaceAction(unrealId) then
        local replaceElement = dbase["replace_element"];
        if type(replaceElement) == "table" then
            local class = DungeonM.queryElement(replaceElement["element"], "class");
            ret[replaceElement["pos"] + 1] = { [class] = replaceElement["element"]};
        end
    end

    return ret;
end

-- 抽取元素
function fetchElement(class, dungeonId, unrealId)
    local elem = DungeonM.getElementsByDungeon("unreal_" .. unrealId) or {};
    local elem1 = DungeonM.getElementsByDungeon(dungeonId) or {};
    local elem2 = DungeonM.getElementsByDungeon("*") or {};

    local arr = elem[class] or {};
    if #arr <= 0 then
        arr = table.append(elem1[class] or {}, elem2[class] or {});
    end

    if #arr <= 0 then
        return 0;
    end

    local rand = DungeonM.getRandSeed("fetch_unreal_element") % #arr;

    -- 排序一遍
    table.sort(arr, function(a, b) return a.id < b.id end);

    return arr[rand + 1]["id"];
end

function fetchBossGrids(dungeonId, unrealId)
    local bonusElems = DungeonBossM.query(dungeonId, "bonus_elems");
    if not bonusElems then
        return {};
    end

    -- 先抽取奖励
    local bonus = fetchBossBonusElems(dungeonId, table.deepcopy(bonusElems));
    local index = 1;

    local ret = {};
    local layout = query(unrealId, "layout");
    for pos = 1, GRID_SIZE do
        -- 客户端的位置应该比服务器+1
        local class = layout[pos - 1];
        if class then
            -- 如果是宝箱
            if DungeonM.query(class, "type") == GRID_TYPE_BOSS_BOX and
                bonus[index] then
                ret[pos] = { [class] = bonus[index], };
                index = index + 1;
            else
                ret[pos] = { [class] = fetchElement(class, dungeonId, unrealId), };
            end
        else
            ret[pos] = { [GRID_TYPE_EMPTY] = 0, };
        end
    end

    return ret;
end

-- 批量抽取
function batchSelect(arr, count)
    table.sort(arr);

    count = math.min(#arr, count);

    local num = #arr;
    if count == num then
        return arr;
    end

    local ret = {};
    repeat
        local index = DungeonM.getRandSeed("unreal_select") % num + 1;

        ret[count] = arr[index];

        arr[index] = arr[num];

        num = num - 1;
        count = count - 1;
    until count <= 0;

    return ret;
end

-- 抽取boss宝箱奖励
function fetchBossBonusElems(dungeonId, bonusElems)
    -- 和服务器一致
    local bonus = {};

    -- s类3件
    local elems = bonusElems["types"];
    bonus = table.append(bonus, batchSelect(elems, 3));

    -- 碎片需要替换为食材
    bonus[2] = bonus[1];

    -- A类2件
    local elema = bonusElems["typea"];
    bonus = table.append(bonus, batchSelect(elema, 2));

    -- B类1件
    local elemb = bonusElems["typeb"];
    bonus = table.append(bonus, batchSelect(elemb, 1));

    -- C类2件
    local elemc = bonusElems["typec"];
    bonus = table.append(bonus, batchSelect(elemc, 2));

    -- D类2件
    local elemd = bonusElems["typed"];
    bonus = table.append(bonus, batchSelect(elemd, 2));

    table.sort(bonus);

    return bonus;
end

-- 是否有奖励未拾取
function hasBonusNotPickedUp()
    -- 如果在角斗台中
    if ArenaM.isInArena() then
        return ArenaM.hasBonusNotTaken();
    end

    local grids = DungeonM.getCurrentDungeon();
    for _, grid in pairs(grids) do
        if (grid:isMonster() and grid.monster:isDead() and grid.bonus) or
           (grid.type == GRID_TYPE_ITEM and grid.bonus) then
            return true;
        end
    end

    -- 活动副本
    if ActivityDungeonM.isInActivityDungeon() then
        local unrealId = UnrealDungeonM.getUnrealId();
        local dbase = UnrealDungeonM.query(unrealId, "dbase");
        if dbase["auto_pick_bonus"] then
            if DungeonM.allMonsterIsDead() then
                return ActivityDungeonM.haveBonus();
            else
                return false;
            end
        end
    end

    return false;
end

function isInAltarRuins()
    local id = getUnrealId();
    if table.indexOf(ALTAR_RUINS_ID, id) == -1 then
        return false;
    else
        return true;
    end
end

-- 过滤失效矿脉
function fitterLayout(layout)
    local indexList = DragonWishM.getInvalidDiamond();

    for index, v in pairs(indexList) do
        layout[v-1] = nil;
    end

    return layout;
end

-- 统计抽取过的特殊奖励
-- 注：这里的统计是临时的，在出夹层后就清除
function statSpecialBonus(field, value)
    local stat = ME.user.dbase:queryTemp("unreal_special_stat") or {};
    stat[field] = value;
    ME.user.dbase:setTemp("unreal_special_stat", stat);
end

-- 获取特殊奖励统计
function getSpecialBonusStat(field)
    local stat = ME.user.dbase:queryTemp("unreal_special_stat") or {};
    return stat[field];
end

-- 你的雕像奖励
function unrealStatueReward(pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 不是你的雕像
    if grid.type ~= GRID_TYPE_UNREAL_STATUE then
        trace("UnrealDungeonM", "格子(%d)不是你的雕像", pos);
        return false;
    end

    -- 已经失效，不能领取
    if grid.state == GRID_STATE_DISABLE then
        trace("UnrealDungeonM", "格子(%d)已经失效。", pos);
        return false;
    end

    local class = grid.class;
    local bonus = FormulaM.invoke("CALC_DRAGON_WISH_BUILDING_BONUS", class);

    for _, perBonus in pairs(bonus) do
        -- 奖励
        BonusM.doBonus(perBonus, "dragon_wish_bonus");
    end

    -- 把格子设置为失效
    grid:changeState(GRID_STATE_DISABLE);
    return bonus;
end

-- 离开夹层的处理
whenLeaveUnreal = function()
    if ME.user.dbase:queryTemp("unreal_special_stat") then
        ME.user.dbase:deleteTemp("unreal_special_stat");
    end
end

-- 设置当前迷宫进度
function setCurDungeonProgress()
    -- 不在迷宫夹层中
    if not isInUnreal() then
        return;
    end
    -- 这里暂时只有一个需要记录进度的，先简单处理下
    -- 之后需要记录夹层进度的可以配表拓展
    DungeonM.setDungeonMixedField("alice_progress", 1);
end

-- 根据进度，检查是否要替换格子
function checkReplaceAction(unrealId)
    if unrealId == 21 and DungeonM.getDungeonMixedField("alice_progress") == 1 then
        return true;
    end

    return false;
end

-- 是否可以复活
function canRevive()
    if not isInUnreal() then
        return true;
    end

    local unrealId = getUnrealId();
    local dbase = query(unrealId, "dbase") or {};
    if dbase["revive"] == 0 then
        -- 配置了不能复活
        return false;
    end

    return true;
end